﻿// Copyright (c) 2012 Mokah Nnamezie & Andrew Cooper
// Neo4Net graph database - http://neo4net.codeplex.com
//
// This file is part of Neo4Net - a .Net port of the Neo4j graph database
//
// Neo4Net is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace Neo4Net.Kernel.Impl.Nioneo.Store
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using log4net;
    using log4net.Core;
    using log4net.Repository.Hierarchy;
    using Neo4Net.Compat.Transaction.Xa;
    using Neo4Net.GraphDb;
    using Neo4Net.GraphDb.Factory;
    using Neo4Net.Helpers.Collection;
    using Neo4Net.Kernel.Configuration;
    using Neo4Net.Kernel.Impl.Core;
    using Neo4Net.Kernel.Impl.Nioneo.Xa;
    using Neo4Net.Kernel.Impl.Transaction;
    using Neo4Net.Kernel.Impl.Transaction.XaFramework;
    using Neo4Net.Kernel.Impl.Util;
    using NUnit.Framework;

    [AbstractNeo4NetTestCase.RequiresPersistentGraphDatabase]
    [TestFixture]
    public partial class TestXa : AbstractNeo4NetTestCase
    {
        private NeoStoreXaDataSource ds;
        private NeoStoreXaConnection xaCon;
        private Logger log;
        private Level level;
        private IDictionary<string, PropertyIndex> propertyIndexes;

        protected override bool RestartGraphDbBetweenTests
        {
            get { return true; }
        }

        private LockManager lockManager;
        private LockReleaser lockReleaser;

        private string DbPath
        {
            get
            {
                string path = GetStorePath("xatest");
                new DirectoryInfo(path).Create();
                return path;
            }
        }

        private string DbFile(string name)
        {
            return Path.Combine(DbPath, name);
        }

        [TestFixtureSetUp]
        public static void DeleteFiles()
        {
            DeleteFileOrDirectory(GetStorePath("xatest"));
        }

        [SetUp]
        public void SetUpNeoStore()
        {
            log = (Logger)LogManager.GetLogger("org.neo4j.kernel.impl.transaction.xaframework.XaLogicalLog/nioneo_logical.log").Logger;
            level = log.EffectiveLevel;
            log.Level = Level.Off;
            log = (Logger)LogManager.GetLogger("org.neo4j.kernel.impl.nioneo.xa.NeoStoreXaDataSource").Logger;
            log.Level = Level.Off;
            DeleteFileOrDirectory(new DirectoryInfo(DbPath));
            propertyIndexes = new Dictionary<string, PropertyIndex>();

            IFileSystemAbstraction fileSystem = new DefaultFileSystemAbstraction();
            StoreFactory sf = new StoreFactory(new Config(new ConfigurationDefaults(typeof(GraphDatabaseSettings)).Apply(new Dictionary<string, string>())),
                                               new DefaultIdGeneratorFactory(), fileSystem, null, StringLogger.DevNull, null);
            sf.CreateNeoStore(DbFile("neo")).Close();

            lockManager = EmbeddedGraphDb.LockManager;
            lockReleaser = EmbeddedGraphDb.LockReleaser;
            ds = NewNeoStore();
            xaCon = ds.XaConnection;
        }

        [TearDown]
        public void TearDownNeoStore()
        {
            ds.Close();
            log.Level = level;
            log = (Logger)LogManager.GetLogger("org.neo4j.kernel.impl.transaction.xaframework.XaLogicalLog/nioneo_logical.log").Logger;
            log.Level = level;
            log = (Logger)LogManager.GetLogger("org.neo4j.kernel.impl.nioneo.xa.NeoStoreXaDataSource").Logger;
            log.Level = level;
            FileInfo file = new FileInfo(DbFile("neo"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.nodestore.db"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.nodestore.db.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db.index"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db.index.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db.index.keys"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db.index.keys.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db.strings"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db.strings.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db.arrays"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.propertystore.db.arrays.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.relationshipstore.db"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.relationshipstore.db.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.relationshiptypestore.db"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.relationshiptypestore.db.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.relationshiptypestore.db.names"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("neo.relationshiptypestore.db.names.id"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            var dir = new DirectoryInfo(DbPath);
            foreach (FileInfo nioFile in dir.EnumerateFiles())
            {
                if (nioFile.Name.StartsWith("nioneo_logical.log"))
                {
                    Assert.That(() => nioFile.Delete(), Throws.Nothing);
                }
            }
        }

        private void DeleteLogicalLogIfExist()
        {
            FileInfo file = new FileInfo(DbFile("nioneo_logical.log.1"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("nioneo_logical.log.2"));
            if (file.Exists)
            {
                Assert.That(() => file.Delete(), Throws.Nothing);
            }
            file = new FileInfo(DbFile("nioneo_logical.log.active"));
            Assert.That(() => file.Delete(), Throws.Nothing);
        }

        public static void RenameCopiedLogicalLog(string storeDir)
        {
            foreach (FileInfo file in new DirectoryInfo(storeDir).EnumerateFiles())
            {
                if (file.Name.Contains(".bak."))
                {
                    string nameWithoutBak = file.Name.Replace(".bak", "");
                    FileInfo targetFile = new FileInfo(Path.Combine(file.DirectoryName, nameWithoutBak));
                    if (targetFile.Exists)
                    {
                        targetFile.Delete();
                    }
                    Assert.That(() => File.Move(file.FullName, targetFile.FullName), Throws.Nothing);
                }
            }
        }

        private void TruncateLogicalLog(int size)
        {
            char active = '1';
            using (FileStream af = File.Open(DbFile("nioneo_logical.log.active"), FileMode.Open, FileAccess.Read))
            {
                byte[] buffer = new byte[1024];
                af.Read(buffer, 0, buffer.Length);
                af.Close();
                active = System.Text.Encoding.UTF8.GetString(buffer)[0];
                FileStream fileStream = File.Open(DbFile("nioneo_logical.log." + active), FileMode.OpenOrCreate, FileAccess.ReadWrite);
                if (fileStream.Length > size)
                {
                    fileStream.SetLength(size);
                }
                else
                {
                    fileStream.Position = size;
                    fileStream.WriteByte(0);
                }
                fileStream.Flush();
                fileStream.Close();
            }
        }

        public static Tuple<Tuple<FileInfo, FileInfo>, Tuple<FileInfo, FileInfo>> CopyLogicalLog(string storeDir)
        {
            char active = '1';
            FileInfo activeLog = new FileInfo(Path.Combine(storeDir, "nioneo_logical.log.active"));
            FileInfo activeLogBackup = new FileInfo(Path.Combine(storeDir, "nioneo_logical.log.bak.active"));
            byte[] buffer = new byte[1024];
            using (FileStream af = File.Open(activeLog.FullName, FileMode.Open, FileAccess.Read))
            using (FileStream activeCopy = File.Open(activeLogBackup.FullName, FileMode.Append, FileAccess.ReadWrite))
            {
                var bytesRead = af.Read(buffer, 0, buffer.Length);
                activeCopy.Write(buffer, 0, bytesRead);
                activeCopy.Close();
                af.Close();
            }
            active = System.Text.Encoding.UTF8.GetString(buffer)[0];
            Array.Clear(buffer, 0, buffer.Length);
            FileInfo currentLog = new FileInfo(Path.Combine(storeDir, "nioneo_logical.log." + active));
            FileInfo currentLogBackup = new FileInfo(Path.Combine(storeDir, "nioneo_logical.log.bak." + active));
            using (FileStream source = File.Open(currentLog.FullName, FileMode.Open, FileAccess.Read))
            using (FileStream dest = File.Open(currentLogBackup.FullName, FileMode.Append, FileAccess.ReadWrite))
            {
                source.CopyTo(dest, 1024);
                source.Close();
                dest.Close();
            }
            return Tuple.Create(Tuple.Create(activeLog, activeLogBackup), Tuple.Create(currentLog, currentLogBackup));
        }

        private PropertyIndex Index(string key)
        {
            PropertyIndex result;
            if (propertyIndexes.TryGetValue(key, out result))
                return result;

            int id = (int)ds.NextId(typeof(PropertyIndex));
            PropertyIndex index = new MyPropertyIndex(key, id);
            propertyIndexes.Add(key, index);
            xaCon.WriteTransaction.CreatePropertyIndex(key, id);
            return index;
        }

        [Test]
        public void TestLogicalLog()
        {
            IXid xid = new XidImpl(new byte[1], new byte[1]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            long node2 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node2);
            IPropertyData n1prop1 = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop1"), "string1");
            xaCon.WriteTransaction.NodeLoadProperties(node1, false);
            int relType1 = (int)ds.NextId(typeof(IRelationshipType));
            xaCon.WriteTransaction.CreateRelationshipType(relType1, "relationshiptype1");
            long rel1 = ds.NextId(typeof(IRelationship));
            xaCon.WriteTransaction.RelationshipCreate(rel1, relType1, node1, node2);
            IPropertyData r1prop1 = xaCon.WriteTransaction.RelAddProperty(rel1, Index("prop1"), "string1");
            n1prop1 = xaCon.WriteTransaction.NodeChangeProperty(node1, n1prop1, "string2");
            r1prop1 = xaCon.WriteTransaction.RelChangeProperty(rel1, r1prop1, "string2");
            xaCon.WriteTransaction.NodeRemoveProperty(node1, n1prop1);
            xaCon.WriteTransaction.RelRemoveProperty(rel1, r1prop1);
            xaCon.WriteTransaction.RelDelete(rel1);
            xaCon.WriteTransaction.NodeDelete(node1);
            xaCon.WriteTransaction.NodeDelete(node2);
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Commit(xid, true);
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);
            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(0));
            xaCon.ClearAllTransactions();
        }

        private NeoStoreXaDataSource NewNeoStore()
        {
            IFileSystemAbstraction fileSystem = new DefaultFileSystemAbstraction();
            Config config = new Config(new ConfigurationDefaults(typeof(GraphDatabaseSettings)).Apply(DictionaryUtil.StringMap(
                AbstractGraphDatabase.Configuration.StoreDir.Name, DbPath,
                AbstractGraphDatabase.Configuration.NeoStore.Name, DbFile("neo"),
                AbstractGraphDatabase.Configuration.LogicalLog.Name, DbFile("nioneo_logical.log"))));

            StoreFactory sf = new StoreFactory(config, new DefaultIdGeneratorFactory(), fileSystem, null, StringLogger.DevNull, null);

            PlaceboTm txManager = new PlaceboTm();
            ILogBufferFactory logBufferFactory = new DefaultLogBufferFactory();
            return new NeoStoreXaDataSource(config, sf, fileSystem, lockManager, lockReleaser, StringLogger.DevNull,
                    new XaFactory(config, TxIdGenerator.Default, txManager,
                            logBufferFactory, fileSystem, StringLogger.DevNull, RecoveryVerifier.AlwaysValid),
                            Enumerable.Empty<Tuple<TransactionInterceptorProvider, Object>>().ToList(), null);
        }

        [Test]
        public void TestLogicalLogPrepared()
        {
            IXid xid = new XidImpl(new byte[2], new byte[2]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            long node2 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node2);
            IPropertyData n1prop1 = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop1"), "string1");
            int relType1 = (int)ds.NextId(typeof(IRelationshipType));
            xaCon.WriteTransaction.CreateRelationshipType(relType1, "relationshiptype1");
            long rel1 = ds.NextId(typeof(IRelationship));
            xaCon.WriteTransaction.RelationshipCreate(rel1, relType1, node1, node2);
            IPropertyData r1prop1 = xaCon.WriteTransaction.RelAddProperty(rel1, Index("prop1"), "string1");
            xaCon.WriteTransaction.NodeChangeProperty(node1, n1prop1, "string2");
            xaCon.WriteTransaction.RelChangeProperty(rel1, r1prop1, "string2");
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            ds.RotateLogicalLog();
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);
            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(1));
            xaRes.Commit(xid, true);
            xaCon.ClearAllTransactions();
        }

        [Test]
        public void TestLogicalLogPreparedPropertyBlocks()
        {
            IXid xid = new XidImpl(new byte[2], new byte[2]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            IPropertyData n1prop1 = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop1"),
                    new long[] { 1 << 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 });
            IPropertyData n1prop2 = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop2"),
                    new long[] { 1 << 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 });
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            ds.RotateLogicalLog();
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);

            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(1));
            xaRes.Commit(xid, true);
            xaCon.ClearAllTransactions();

            xid = new XidImpl(new byte[2], new byte[2]);
            xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            xaCon.WriteTransaction.NodeRemoveProperty(node1, n1prop1);
            xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop3"),
                    new long[] { 1 << 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 });
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            ds.RotateLogicalLog();
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);
            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(1));
            xaRes.Commit(xid, true);
            xaCon.ClearAllTransactions();
        }

        [Test]
        public void MakeSureRecordsAreCreated()
        {
            IXid xid = new XidImpl(new byte[2], new byte[2]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop1"), new long[] { 1 << 63, 1, 1 });
            xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop2"), new long[] { 1 << 63, 1, 1 });
            IPropertyData toRead = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop3"), new long[] { 1 << 63, 1, 1 });
            IPropertyData toDelete = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop4"), new long[] { 1 << 63, 1, 1 });
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Commit(xid, true);
            ds.RotateLogicalLog();
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);

            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;

            xid = new XidImpl(new byte[2], new byte[2]);
            xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);

            xaCon.WriteTransaction.NodeRemoveProperty(node1, toDelete);

            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Commit(xid, true);
            ds.RotateLogicalLog();
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);

            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            xid = new XidImpl(new byte[2], new byte[2]);
            xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);

            CollectionAssert.AreEqual((long[])toRead.Value, (long[])xaCon.WriteTransaction.NodeLoadProperties(node1, false)[toRead.Index].Value);

            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);
        }

        [Test]
        public void TestDynamicRecordsInLog()
        {
            IXid xid = new XidImpl(new byte[2], new byte[2]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            IPropertyData toChange = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop1"), "hi");
            IPropertyData toRead = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop2"),
                    new long[] { 1 << 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 });
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            ds.RotateLogicalLog();
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);

            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(1));
            xaRes.Commit(xid, true);
            xaCon.ClearAllTransactions();
            xid = new XidImpl(new byte[2], new byte[2]);
            xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            xaCon.WriteTransaction.NodeChangeProperty(node1, toChange, "hI");
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            ds.RotateLogicalLog();
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);

            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(1));
            xaRes.Commit(xid, true);
            xaCon.ClearAllTransactions();

            CollectionAssert.AreEqual(new long[] { 1 << 23, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
                    (long[])xaCon.WriteTransaction.LoadPropertyValue(toRead));

        }

        [Test]
        public void TestLogicalLogPrePrepared()
        {
            IXid xid = new XidImpl(new byte[3], new byte[3]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            long node2 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node2);
            IPropertyData n1prop1 = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop1"), "string1");
            int relType1 = (int)ds.NextId(typeof(IRelationshipType));
            xaCon.WriteTransaction.CreateRelationshipType(relType1, "relationshiptype1");
            long rel1 = ds.NextId(typeof(IRelationship));
            xaCon.WriteTransaction.RelationshipCreate(rel1, relType1, node1, node2);
            IPropertyData r1prop1 = xaCon.WriteTransaction.RelAddProperty(rel1, Index("prop1"), "string1");
            xaCon.WriteTransaction.NodeChangeProperty(node1, n1prop1, "string2");
            xaCon.WriteTransaction.RelChangeProperty(rel1, r1prop1, "string2");
            xaRes.End(xid, XaResource.TmSuccess);
            xaCon.ClearAllTransactions();
            CopyLogicalLog(DbPath);
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);
            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(0));
        }

        [Test]
        public void TestBrokenNodeCommand()
        {
            IXid xid = new XidImpl(new byte[4], new byte[4]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            xaCon.ClearAllTransactions();
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);
            TruncateLogicalLog(94);
            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(0));
            xaCon.ClearAllTransactions();
        }

        [Test]
        public void TestBrokenCommand()
        {
            IXid xid = new XidImpl(new byte[4], new byte[4]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            xaCon.ClearAllTransactions();
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);
            TruncateLogicalLog(94);
            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(0));
            xaCon.ClearAllTransactions();
        }

        [Test]
        public void TestBrokenPrepare()
        {
            IXid xid = new XidImpl(new byte[4], new byte[4]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            long node2 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node2);
            xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop1"), "string value 1");
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            CopyLogicalLog(DbPath);
            xaCon.ClearAllTransactions();
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);
            TruncateLogicalLog(243);
            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(0));
            xaCon.ClearAllTransactions();
        }

        [Test]
        public void TestBrokenDone()
        {
            IXid xid = new XidImpl(new byte[4], new byte[4]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            long node2 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node2);
            xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop1"), "string value 1");
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Prepare(xid);
            xaRes.Commit(xid, false);
            CopyLogicalLog(DbPath);
            ds.Close();
            DeleteLogicalLogIfExist();
            RenameCopiedLogicalLog(DbPath);
            TruncateLogicalLog(264);
            ds = NewNeoStore();
            xaCon = ds.XaConnection;
            xaRes = xaCon.XaResource;
            Assert.That(xaRes.Recover(XaResource.TmNoFlags).Length, Is.EqualTo(1));
            xaCon.ClearAllTransactions();
        }

        [Test]
        public void TestLogVersion()
        {
            long creationTime = ds.CreationTime;
            long randomIdentifier = ds.RandomIdentifier;
            long currentVersion = ds.CurrentLogVersion;
            Assert.That(ds.IncrementAndGetLogVersion(), Is.EqualTo(currentVersion));
            Assert.That(ds.IncrementAndGetLogVersion(), Is.EqualTo(currentVersion + 1));
            Assert.That(ds.CreationTime, Is.EqualTo(creationTime));
            Assert.That(ds.RandomIdentifier, Is.EqualTo(randomIdentifier));
        }

        [Test]
        public void TestLogicalLogRotation()
        {
            ds.KeepLogicalLogs(true);
            IXid xid = new XidImpl(new byte[1], new byte[1]);
            XaResource xaRes = xaCon.XaResource;
            xaRes.Start(xid, XaResource.TmNoFlags);
            long node1 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node1);
            long node2 = ds.NextId(typeof(INode));
            xaCon.WriteTransaction.NodeCreate(node2);
            IPropertyData n1prop1 = xaCon.WriteTransaction.NodeAddProperty(node1, Index("prop1"), "string1");
            xaCon.WriteTransaction.NodeLoadProperties(node1, false);
            int relType1 = (int)ds.NextId(typeof(IRelationshipType));
            xaCon.WriteTransaction.CreateRelationshipType(relType1, "relationshiptype1");
            long rel1 = ds.NextId(typeof(IRelationship));
            xaCon.WriteTransaction.RelationshipCreate(rel1, relType1, node1, node2);
            IPropertyData r1prop1 = xaCon.WriteTransaction.RelAddProperty(rel1, Index("prop1"), "string1");
            n1prop1 = xaCon.WriteTransaction.NodeChangeProperty(node1, n1prop1, "string2");
            r1prop1 = xaCon.WriteTransaction.RelChangeProperty(rel1, r1prop1, "string2");
            xaCon.WriteTransaction.NodeRemoveProperty(node1, n1prop1);
            xaCon.WriteTransaction.RelRemoveProperty(rel1, r1prop1);
            xaCon.WriteTransaction.RelDelete(rel1);
            xaCon.WriteTransaction.NodeDelete(node1);
            xaCon.WriteTransaction.NodeDelete(node2);
            xaRes.End(xid, XaResource.TmSuccess);
            xaRes.Commit(xid, true);
            long currentVersion = ds.CurrentLogVersion;
            ds.RotateLogicalLog();
            Assert.That(LogicalLogExists(currentVersion), Is.True);
            ds.RotateLogicalLog();
            Assert.That(LogicalLogExists(currentVersion), Is.True);
            Assert.That(LogicalLogExists(currentVersion + 1), Is.True);
        }

        private bool LogicalLogExists(long version)
        {
            Stream log = ds.GetLogicalLog(version);
            try
            {
                return log != null;
            }
            finally
            {
                log.Close();
            }
        }
    }
}
